package buildings.office;

import buildings.Building;
import buildings.Space;
import buildings.Floor;
import buildings.iterators.OfficeBuildingIterator;
import buildings.exceptions.FloorIndexOutOfBoundsException;
import buildings.exceptions.SpaceIndexOutOfBoundsException;
import java.io.Serializable;
import java.util.Iterator;

public class OfficeBuilding implements Building, Serializable {

    private OfficeFloorNode head;
    private OfficeFloorNode current;
    private int count;

    private OfficeFloorNode getOfficeFloorNodeByNumber(int number) {
        if (number >= count || number < 0) {
            throw new FloorIndexOutOfBoundsException();
        }
        current = head;
        int i = 0;
        while (i != number) {
            current = current.getNext();
            i++;
        }
        return current;
    }

    private void addOfficeFloorNodeByNumber(OfficeFloorNode officeFloor, int number) {
        if (number > count || number < 0) {
            throw new FloorIndexOutOfBoundsException();
        }
        if (number == 0) {
            officeFloor.setNext(head);
            officeFloor.setPrev(head.getPrev());
            head.setPrev(officeFloor);
            head = officeFloor;
        } else {
            current = head;
            int i = 0;
            while (i != number) {
                current = current.getNext();
                i++;
            }
            officeFloor.setNext(current);
            officeFloor.setPrev(current.getPrev());
            current.setPrev(officeFloor);
            officeFloor.getPrev().setNext(officeFloor);
        }
        count++;
    }

    private void deleteOfficeFloorNodeByNumber(int number) {
        if (number > count || number < 0) {
            throw new FloorIndexOutOfBoundsException();
        }
        if (number == 0) {
            head.getNext().setPrev(head.getPrev());
            head = head.getNext();
        } else {
            current = head;
            int i = 0;
            while (i != number) {
                current = current.getNext();
                i++;
            }
            current.getPrev().setNext(current.getNext());
            current.getNext().setPrev(current.getPrev());
        }
        count--;
    }

    public OfficeBuilding(int floorCount, int[] floors) {
        if (floorCount < 1 || floorCount != floors.length) {
            throw new FloorIndexOutOfBoundsException();
        }
        int i = 0;
        head = new OfficeFloorNode(new OfficeFloor(floors[0]));
        head.setNext(head);
        head.setPrev(head);
        count = 1;
        current = head;
        for (i = 1; i < floorCount; i++, count++) {
            current.setNext(new OfficeFloorNode(new OfficeFloor(floors[i])));
            head.setPrev(current.getNext());
            if (i == floorCount - 1) {
                current.getNext().setNext(head);
            } else {
                current = current.getNext();
            }
        }
    }

    public OfficeBuilding(Floor[] floors) {
        if (floors.length < 1) {
            throw new FloorIndexOutOfBoundsException();
        }
        head = new OfficeFloorNode(floors[0]);
        head.setNext(head);
        head.setPrev(head);
        count = 1;
        current = head;
        for (int i = 1; i < floors.length; i++, count++) {
            current.setNext(new OfficeFloorNode(floors[i]));
            head.setPrev(current.getNext());
            if (i == floors.length - 1) {
                current.getNext().setNext(head);
            } else {
                current = current.getNext();
            }
        }
    }

    public int getFloorCount() {
        return count;
    }

    public int getSpaceCount() {
        int officeCount = 0;
        current = head;
        for (int i = 0; i < count; i++) {
            officeCount += current.getElement().getSpaceCount();
            current = current.getNext();
        }
        return officeCount;
    }

    public float getTotalSquare() {
        float totalSquare = 0;
        current = head;
        for (int i = 0; i < count; i++) {
            totalSquare += current.getElement().getTotalSquare();
            current = current.getNext();
        }
        return totalSquare;
    }

    public int getTotalRoomCount() {
        int roomCount = 0;
        current = head;
        for (int i = 0; i < count; i++) {
            roomCount += current.getElement().getTotalRoomCount();
            current = current.getNext();
        }
        return roomCount;
    }

    public Floor[] getFloors() {
        Floor[] floors = new OfficeFloor[count];
        current = head;
        for (int i = 0; i < count; i++) {
            floors[i] = current.getElement();
            current = current.getNext();
        }
        return floors;
    }

    //Функция превращения номер офиса в здании
    //в номер этажа [0] и номер офиса на этаже [1]
    private int[] transformToFloorAndOffice(int number) {
        if(number > getSpaceCount()) {
            throw new SpaceIndexOutOfBoundsException();
        }
        current = head;
        for (int i = 0; i < count; i++) {
            if (number < current.getElement().getSpaceCount()) {
                return new int[]{i, number};
            } else {
                number -= current.getElement().getSpaceCount();
                current = current.getNext();
            }
        }
        //По идее до этой точки программа не дойдёт
        //Нужна, чтобы не ругался компилятор
        return new int[]{0, 0};
    }

    public Floor getFloorByNumber(int number) {
        return getOfficeFloorNodeByNumber(number).getElement();
    }

    public void setFloorByNumber(int number, Floor newFloor) {
        getOfficeFloorNodeByNumber(number).setElement(newFloor);
    }
    
    public Space getSpaceByNumber(int number) {
        int[] tNum = transformToFloorAndOffice(number);
        return getOfficeFloorNodeByNumber(tNum[0]).
                getElement().getSpaceByNumber(tNum[1]);
    }
    
    public void setSpaceByNumber(int number, Space newOffice) {      
        int[] tNum = transformToFloorAndOffice(number);
        getOfficeFloorNodeByNumber(tNum[0]).getElement().
                setSpaceByNumber(tNum[1], newOffice);
    }
    
    public void addSpaceByNumber(int number, Space newOffice) {      
        int[] tNum = transformToFloorAndOffice(number);
        getOfficeFloorNodeByNumber(tNum[0]).getElement().
                addSpaceByNumber(tNum[1], newOffice);
    }
    
    public void deleteSpaceByNumber(int number) {      
        int[] tNum = transformToFloorAndOffice(number);
        getOfficeFloorNodeByNumber(tNum[0]).getElement().
                deleteSpaceByNumber(tNum[1]);
    }
    
    public Space getBestSpace() {
        Space bestSpace = head.getElement().getBestSpace();
        current = head;
        for(int i = 0; i < count; i++) {
            if(bestSpace.getSquare() < current.getElement().getBestSpace().getSquare()) {
                bestSpace = current.getElement().getBestSpace();
            }
            current = current.getNext();
        }
        return bestSpace;
    }
    
    //Быстрая сортировка
    private void quickSort(Space[] offices, int low, int high) {
        int i = low;
        int j = high;
        float x = offices[(low + high) / 2].getSquare();
        do {
            while (offices[i].getSquare() > x) {
                ++i;
            }
            while (offices[j].getSquare() < x) {
                --j;
            }
            if (i <= j) {
                Space temp = offices[i];
                offices[i] = offices[j];
                offices[j] = temp;
                i++;
                j--;
            }
        } while (i <= j);
        if (low < j) {
            quickSort(offices, low, j);
        }
        if (high > i) {
            quickSort(offices, i, high);
        }
    }
    
    public Space[] getSortedSpaces() {
        Space[] offices = new Office[getSpaceCount()];
        current = head;
        for (int floor = 0, cur = 0; floor < count; floor++) {
            for (int office = 0; office < current.getElement().getSpaceCount();
                    office++, cur++) {
                offices[cur] = current.getElement().getSpaceByNumber(office);
            }
            current = current.getNext();
        }
        try {
            quickSort(offices, 0, offices.length - 1);
        }
        catch(ArrayIndexOutOfBoundsException e) {
            throw new SpaceIndexOutOfBoundsException();
        }
        finally {
            return offices;
        }
    }
    
    public Iterator iterator() {
        return new Iterator() {
            private int count = 0;

            public Floor next() {
                return getFloorByNumber(count++);
            }

            public boolean hasNext() {
                return getFloorCount() < count;
            }

            public void remove() throws UnsupportedOperationException {
                throw new UnsupportedOperationException();
            }
        };
    }
    
    public String toString() {
        StringBuilder buf = new StringBuilder(this.getClass().getSimpleName() 
                + " (" + count);
        current = head;
        for(int i = 0; i < count; i++) {
            buf.append(" ");
            buf.append(current.getElement());
            current = current.getNext();
        }
        buf.append(")");
        return buf.toString();
    }
    
    public boolean equals(Object obj) {
        if(!(obj instanceof OfficeBuilding))
            return false;
        boolean isFloorsEquals = true;
        current = head;
        for (int i = 0; i < count; i++) {
            if (!((OfficeBuilding) obj).getFloorByNumber(i).equals(current.getElement())) {
                isFloorsEquals = false;
                break;
            }
            current = current.getNext();
        }
        return ((((OfficeBuilding) obj).getFloorCount() == count)
                && (isFloorsEquals));
    }
    
    public int hashCode() {
        int hash = count;
        for(int i = 0; i < count; i++) {
            hash |= getFloorByNumber(i).hashCode();
        }
        return hash;
    }

    public Object clone() throws CloneNotSupportedException {
        Object result = super.clone();
        ((OfficeBuilding)result).head = (OfficeFloorNode)head.clone();
        ((OfficeBuilding)result).current = (OfficeFloorNode)current.clone();
        return result;
    }
    
    
}
